/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: kfa_player.c
 *
 * Purpose: implementation a playerer
 *
 * Developer:
 *   wen.gu , 2022-03-04
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "kfa_player.h"

#include <chrono>
#if defined(HAS_QNX_SCREEN) && HAS_QNX_SCREEN
#include "qnx_render.h"
#elif defined(_MSC_VER)
#include "win_render.h"    
#else
#endif
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define LOGD printf
#define LOGE printf
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static uint64_t NowMs()
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

static void SleepMs(uint32_t ms)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

static uint32_t get_frame_size(kfa_raw_type raw_type, uint32_t width, uint32_t height)
{
    switch (raw_type)
    {
    case kfa_raw_type::RGBA32: return width * height * 4;     break;
    case kfa_raw_type::RGB24:  return width * height * 3;     break;
    case kfa_raw_type::NV12:   return width * height * 3 / 2; break;
    default: break;
    }

    return 0;
}


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

KfaPlayer::KfaPlayer()
{
    //todo something
#if defined(HAS_QNX_SCREEN) && HAS_QNX_SCREEN
    render_ = new QnxRender;
#elif defined(_MSC_VER)
    render_ = new WinRender;
#else
#endif
}

KfaPlayer::~KfaPlayer()
{
    //todo something
    stop();   

    if (decoder_)
    {
        delete decoder_;
    }

    if (render_)
    {
        delete render_;
    } 
}

bool KfaPlayer::setDataSource(const std::string& file_url)
{
    if (demux_.setDataSource(file_url) == false)
    {
        LOGE("kfa demux set data source(%s) failed\n", file_url.c_str());
        return false;
    }

    if (demux_.getHeader(header_) == false)
    {
        LOGE("kfa demux get header(%s) failed\n", file_url.c_str());
        return false;        
    }

    decoder_ = KfaDecoder::create(header_.codec_format);

    if (!decoder_)
    {
        LOGE("unsupported codec format(%d) for file(%s) \n", header_.codec_format,  file_url.c_str());
        return false;
    }

    KfaDecoderParam dec_param;
    //uint32_t frame_size = get_frame_size(header_.raw_type, header_.width, header_.height);
    dec_param.pic_width = header_.width;
    dec_param.pic_height = header_.height;
    dec_param.raw_type = header_.raw_type;

    if (decoder_->setConfigure(dec_param) == false)
    {
        LOGE("kfa configure decoder failed with(w: %d, h: %d, raw type: %d) for file(%s) \n", header_.width, header_.height, header_.raw_type,  file_url.c_str());
        return false;
    }

    return true;
}

bool KfaPlayer::setPlayerParameter(const render_param& param)
{
    render_param_ = param;
    
    return render_->initialize(param);
}

bool KfaPlayer::setDisplaySize(uint32_t pos_x, uint32_t pos_y, uint32_t width, uint32_t height)
{
    display_pos_x_ = pos_x;
    display_pos_y_ = pos_y;
    display_width_ = width;
    display_height_ = height;
    return true;
}

bool KfaPlayer::start()
{
    if (is_running_)
    {
        return false;
    }
#if 0
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    if (init_render(screen_ctx_, screen_win_, render_param_) ==  false)
    {
        return false;
    }
#endif

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    is_running_ = true;    

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    play_thd_ = new std::thread([this]()
    {
        this->onPlay();
    });
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    is_playing_ = true;
    return true;
}

bool KfaPlayer::stop()
{
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    if (!is_running_)
    {
        return false;
    }

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    is_running_ = false;
    if (play_thd_)
    {
        play_thd_->join();
        delete play_thd_;
        play_thd_ = nullptr;
    }
#if 0
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    if (render_thd_)
    {
        render_thd_->join();
        delete render_thd_;
        render_thd_ = nullptr;
    }
#endif
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    is_playing_ = false;
    return true;
}

bool KfaPlayer::is_playing()
{
    return is_playing_;
}

#if 0
void KfaPlayer::onRender()
{
    uint32_t read_idx = 0;
    uint32_t dst_idx = 0;
    uint64_t st = NowMs();  
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    while (is_running_)
    {
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        KfaBuffer* buf = buffer_queue_.pop();
        //LOGD("[%s.%d]===>: %p\n", __FUNCTION__, __LINE__, buf);
        if (buf) /** todo refine me??  to support other raw type, now only NV12 supported */
        {
            uint32_t idx = 0;
            uint32_t count = buf->frame_count();
            uint32_t frame_size = get_frame_size(buf->raw_type(), buf->frame_width(), buf->frame_height());
            screen_buffer_t screen_buf = nullptr;
            int32_t src_pos = buf->frame_width() * buf->frame_height();
            int32_t uv_size = src_pos / 2;
            uint8_t* cur_buf = buf->data();
            int32_t uv_pos = 0;
            //LOGD("[%s.%d]===>: src_pos: %d, width: %d, height: %d, raw_type: %d, uv_size: %d, frame_size: %d, in size: %d, frame_count: %d\n", __FUNCTION__, __LINE__, 
            //    src_pos, buf->frame_width(), buf->frame_height(), buf->raw_type(), uv_size, frame_size, buf->size(), count);

            if (!screen_ctx_)
            {
                render_param_.img_width = buf->frame_width();
                render_param_.img_height = buf->frame_height();
                if (init_render(screen_ctx_, screen_win_, render_param_) ==  false)
                {
                    LOGE("init render failed\n");
                    buf->release();
                    continue;;
                    //return false;
                }                
            }

            while (idx < count)
            {
                uint8_t* dst_buf = get_render_buffer(screen_win_, screen_buf, uv_pos);

                if (!dst_buf)
                {
                    LOGE("get render buffer failed\n");
                    continue;
                }    
                //LOGD("[%s.%d]===>:dst_buf: %p, cur_buf: %p, src_pos: %d, uv_pos:%d, uv_size: %d\n", __FUNCTION__, __LINE__,
                //    dst_buf, cur_buf, src_pos, uv_pos, uv_size);
                memcpy(dst_buf, cur_buf, src_pos);
                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
                memcpy(dst_buf + uv_pos, cur_buf + src_pos, uv_size);                    
                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
                int rect[4] = {0, 0, buf->frame_width(), buf->frame_height()};
                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
                int rc = screen_post_window(screen_win_, screen_buf, 1, rect, 0); //SCREEN_WAIT_IDLE
                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
                if (rc != 0)
                {
                    LOGE("Post image to window error, the error code is %d, and error message is %s.\n", rc, strerror(errno));
                    break;
                }   

                cur_buf += frame_size;
                idx++;

                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
                uint64_t ct = NowMs() - st;

                if (ct >= 1000)
                {
                    LOGD("fps: %d, max_dec: %d, idx: %d\n", show_cnt_, max_dec_time_, max_dec_idx_);
                    st = NowMs();
                    show_cnt_ = 0;
                    max_dec_time_ = 0;
                }
                else
                {
                    show_cnt_++;
                } 
                SleepMs(render_param_.frame_interval_ms);
                //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__); 
            }
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            buf->release();
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        }            
    }
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
}

void KfaPlayer::onDecode()
{
    uint32_t read_idx = 0;
    uint32_t dst_idx = 0;
    uint64_t st = NowMs();  
    uint32_t dst_size = get_frame_size(header_.raw_type, header_.width, header_.height);
    kfa_packet block = {0};
    bool ret = false;
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    while (is_running_)
    {        
        //bool ret = demux_.read(src_buf, src_size);
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        if (!block.size)
        {
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            ret = demux_.read(block);
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            if (!ret)
            {   
                LOGD("[%s.%d]===>: demux eof\n", __FUNCTION__, __LINE__);         
                if (render_param_.repeat)
                {
                    demux_.reset(); //todo 
                    continue;                
                }
                else
                {
                    LOGE("demux_ read failed, maybe eof\n");
                    is_playing_ = false;
                    break;
                }
            }            
        }

        uint64_t dec_st = NowMs();
        KfaBuffer* out_buffer = nullptr;
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        ret = decoder_->decode(block, out_buffer);
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        if (!ret)
        {
            //LOGE("decode error, try again\n");
            continue;
        }     

        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        block.size = 0;
        out_buffer->set_raw_type(header_.raw_type);
        out_buffer->set_frame_count(block.frame_count);
        out_buffer->set_frame_size(header_.width, header_.height);
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        buffer_queue_.push(out_buffer);   
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        uint64_t dec_ct = NowMs() - st;

        if (dec_ct > max_dec_time_)
        {
            max_dec_time_ = dec_ct;
            max_dec_idx_ = show_cnt_;
        }
    }
    //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
}
#endif

void KfaPlayer::onPlay()
{
    uint32_t read_idx = 0;
    uint32_t dst_idx = 0;
    uint64_t st = NowMs();  
    uint32_t dst_size = get_frame_size(header_.raw_type, header_.width, header_.height);
    
    bool ret = false;
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    uint8_t* pkt_data = nullptr;
    uint32_t pkt_size = 0;
    while (is_running_)
    {       

        if (!pkt_data)
        {
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            ret = demux_.read(pkt_data, pkt_size);
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            if (!ret)
            {   
                //LOGD("[%s.%d]===>: demux eof\n", __FUNCTION__, __LINE__);         
                if (render_param_.repeat)
                {
                    demux_.reset(); //todo 
                    continue;                
                }
                else
                {
                    LOGE("demux_ read failed, maybe eof\n");
                    is_playing_ = false;
                    break;
                }
            }            
        }

        uint64_t dec_st = NowMs();

        KfaBuffer out_buf;
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        ret = decoder_->decode(pkt_data, pkt_size, out_buf);
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        if (!ret)
        {
            LOGE("decode error, try again\n");
            continue;
        }     

        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        delete[] pkt_data;
        pkt_data = nullptr;
        


        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        uint64_t dec_ct = NowMs() - dec_st;

        if (dec_ct > max_dec_time_)
        {
            max_dec_time_ = dec_ct;
            max_dec_idx_ = show_cnt_;
        }

        uint64_t disp_st = NowMs();
        ret = render_->render(out_buf);

        uint64_t disp_ct = NowMs() - disp_st;

        if (disp_ct > max_disp_time_)
        {
            max_disp_time_ = disp_ct;
            //max_dec_idx_ = show_cnt_;
        }

        out_buf.release(out_buf); //todo ,refine me??

        uint64_t ct = NowMs() - st;

        if (ct >= 1000)
        {
            LOGD("fps: %d, max_dec: %lld(ms), idx: %d, max_disp: %lld(ms)\n", show_cnt_, max_dec_time_, max_dec_idx_, max_disp_time_);
            st = NowMs();
            show_cnt_ = 0;
            max_dec_time_ = 0;
            max_disp_time_ = 0;
        }
        else
        {
            show_cnt_++;
        } 
        SleepMs(render_param_.frame_interval_ms);

    }
}
